package com.dragon.sso.config;

import com.dragon.sso.config.base.AuthServerConfiguration;
import com.google.common.collect.Sets;
import java.security.KeyPair;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Set;
import javax.sql.DataSource;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.security.oauth2.provider.client.InMemoryClientDetailsService;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.endpoint.DefaultRedirectResolver;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

/**
 * Create date 2020/6/15. 授权服务器配置
 *
 * <p>下面在列出OAuth2的一些默认端点：
 *
 * <p>/oauth/authorize：授权端点，用于grant_type为Authorization Code时，获取授权码。 /oauth/token：令牌端点，用于获取Access
 * Token。 /oauth/confirm_access：用于grant_type为Authorization Code时，用户确认授权提交端点。
 * /oauth/error：授权服务错误信息端点。 /oauth/check_token：用于资源服务访问的令牌解析端点。 /oauth/token_key：在JwtTokenStore模式下提供公有密匙的端点。
 *
 * @author evan
 */
@Configuration
@EnableAuthorizationServer
public class AuthConfiguration extends AuthorizationServerConfigurerAdapter
    implements ApplicationContextAware {

  private final AuthServerConfiguration authServerConfiguration;
  private final AuthenticationManager authenticationManager;
  private final DataSource dataSource;
  private ApplicationContext applicationContext;

  public AuthConfiguration(
      AuthServerConfiguration authServerConfiguration,
      @Qualifier("dataSource") DataSource dataSource,
      @Qualifier("authenticationManagerBean") AuthenticationManager authenticationManager) {
    this.authServerConfiguration = authServerConfiguration;
    this.dataSource = dataSource;
    this.authenticationManager = authenticationManager;
  }

  @Override
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    this.applicationContext = applicationContext;
  }

  /**
   * 配置客户端秘钥对的数据源。
   *
   * @param clients
   * @throws Exception
   */
  @Override
  public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients.withClientDetails(inMemoryClientDetailsService());
  }

  /**
   * 配置OAuth2 协议的接口列表 来配置授权（authorization） - 令牌（token）的访问端点 （配置申请令牌的地址） - 令牌服务(token services)
   * （发放令牌的方式）
   *
   * @param endpoints
   * @throws Exception
   */
  @Override
  public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    // 登陆成功，request_uri 匹配
    DefaultRedirectResolver defaultRedirectResolver = new DefaultRedirectResolver();
    endpoints.redirectResolver(defaultRedirectResolver);

    JdbcAuthorizationCodeServices jdbcAuthorizationCodeServices =
        new JdbcAuthorizationCodeServices(dataSource);
    endpoints.authorizationCodeServices(jdbcAuthorizationCodeServices);

    // pick up all  TokenEnhancers incl. those defined in the application
    // this avoids changes to this class if an application wants to add its own to the chain
    Collection<TokenEnhancer> tokenEnhancers =
        applicationContext.getBeansOfType(TokenEnhancer.class).values();
    TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
    tokenEnhancerChain.setTokenEnhancers(new ArrayList<>(tokenEnhancers));
    endpoints
        .authenticationManager(authenticationManager)
        .tokenStore(tokenStore())
        .tokenEnhancer(tokenEnhancerChain)
        .reuseRefreshTokens(false); // don't reuse or we will run into session inactivity timeouts
  }

  /**
   * 对remember me 的token进行持久化
   *
   * @return
   */
  @Bean
  public PersistentTokenRepository persistentTokenRepository() {
    JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
    tokenRepository.setDataSource(dataSource);
    return tokenRepository;
  }

  /**
   * Apply the token converter (and enhancer) for token store.
   *
   * @return the {@link JwtTokenStore} managing the tokens.
   */
  @Bean
  public JwtTokenStore tokenStore() {
    return new JwtTokenStore(jwtAccessTokenConverter());
  }

  /**
   * access token 解析器
   *
   * <p>1. 加载类路径中的密钥库，用于读取存放在密钥库中的公钥
   *
   * @return an access token converter configured with the authorization server's public/private
   * keys.
   */
  @Bean
  public JwtAccessTokenConverter jwtAccessTokenConverter() {
    DefaultAccessTokenConverter defaultAccessTokenConverter = new DefaultAccessTokenConverter();
    JwtAccessTokenConverter converter = new CustomJwtAccessTokenConverter();
    converter.setAccessTokenConverter(defaultAccessTokenConverter);
    KeyPair keyPair = keyPair();
    converter.setKeyPair(keyPair);
    return converter;
  }

  @Bean
  public KeyPair keyPair() {
    KeyPair keyPair =
        new KeyStoreKeyFactory(
            new ClassPathResource(authServerConfiguration.getKeyStore().getName()),
            authServerConfiguration.getKeyStore().getPassword().toCharArray())
            .getKeyPair(authServerConfiguration.getKeyStore().getAlias());
    return keyPair;
  }

  /**
   * 配置令牌端点的安全约束
   *
   * @param oauthServer
   * @throws Exception
   */
  @Override
  public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
    // 启用请求auth 验证
    oauthServer
        .tokenKeyAccess("permitAll()") // 任何用户都可以去获取token
        .checkTokenAccess("isAuthenticated()") // 认证过的用户才能去检查token
        .allowFormAuthenticationForClients(); // 允许表单认证
  }

  /**
   * 配置client客戶端
   *
   * @return
   */
  public InMemoryClientDetailsService inMemoryClientDetailsService() {
    HashMap<String, BaseClientDetails> map = new HashMap<>();
    PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
    authServerConfiguration.getRegistrations().stream().forEach(r -> {
      BaseClientDetails clientDetails = new BaseClientDetails();
      clientDetails.setClientSecret(passwordEncoder.encode(r.getClientSecret()));
      Set<String> authorities = r.getAuthorities();
      if (authorities == null) {
        authorities = Sets.newHashSet("USER");
      }
      clientDetails.setAuthorities(AuthorityUtils.createAuthorityList(
          authorities.toArray(new String[authorities.size()])));
      clientDetails.setClientId(r.getClientId());
      if (r.isAutoScope()) {
        clientDetails.setAutoApproveScopes(r.getScope());
      }
      clientDetails.setScope(r.getScope());
      clientDetails.setRegisteredRedirectUri(Sets.newHashSet(r.getRedirectUri()));
      clientDetails.setAuthorizedGrantTypes(r.getAuthorizedGrantTypes());
      clientDetails.setResourceIds(r.getResourceIds());
      map.put(r.getClientId(), clientDetails);
    });
    InMemoryClientDetailsService detailsService = new InMemoryClientDetailsService();
    detailsService.setClientDetailsStore(map);
    return detailsService;
  }
}
